package net.xdclass.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import net.xdclass.component.PayFactory;
import net.xdclass.config.OrderMqConfig;
import net.xdclass.constant.CacheKey;
import net.xdclass.constant.TimeOutConstant;
import net.xdclass.enums.BizCodeEnum;
import net.xdclass.enums.ProductOrderPayTypeEnum;
import net.xdclass.enums.ProductOrderStateEnum;
import net.xdclass.enums.ProductOrderTypeEnum;
import net.xdclass.exception.BizException;
import net.xdclass.feign.CouponFeignClient;
import net.xdclass.feign.ProductFeignClient;
import net.xdclass.feign.UserFeignClient;
import net.xdclass.interceptor.LoginInterceptor;
import net.xdclass.mapper.ProductOrderItemMapper;
import net.xdclass.mapper.ProductOrderMapper;
import net.xdclass.model.LoginUser;
import net.xdclass.model.ProductOrderDO;
import net.xdclass.model.ProductOrderItemDO;
import net.xdclass.request.ConfirmOrderRequest;
import net.xdclass.request.LockCouponRecordRequest;
import net.xdclass.request.LockProductRequest;
import net.xdclass.request.OrderItemRequest;
import net.xdclass.request.OrderMessage;
import net.xdclass.request.RepayOrderRequest;
import net.xdclass.service.ProductOrderService;
import net.xdclass.util.Builder;
import net.xdclass.util.CommonUtil;
import net.xdclass.util.JsonData;
import net.xdclass.vo.OrderCouponRecordVo;
import net.xdclass.vo.OrderItemVo;
import net.xdclass.vo.PayInfoVO;
import net.xdclass.vo.ProductOrderAddressVo;
import net.xdclass.vo.ProductOrderItemVo;
import net.xdclass.vo.ProductOrderVo;
import org.apache.commons.lang3.StringUtils;
import org.assertj.core.util.Lists;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;


/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author 孙浩
 * @since 2022-05-11
 */
@Slf4j
@Service
public class ProductOrderServiceImpl implements ProductOrderService {

  @Autowired
  private ProductOrderMapper productOrderMapper;

  @Autowired
  private UserFeignClient userFeignClient;

  @Autowired
  private ProductFeignClient productFeignClient;

  @Autowired
  private CouponFeignClient couponFeignClient;

  @Autowired
  private ProductOrderItemMapper productOrderItemMapper;

  @Autowired
  private RabbitTemplate rabbitTemplate;

  @Autowired
  private OrderMqConfig orderMqConfig;

  @Autowired
  private PayFactory payFactory;

  @Autowired
  private StringRedisTemplate stringRedisTemplate;

  /**
   * * 防重提交
   * * 用户微服务-确认收货地址
   * * 商品微服务-获取最新购物项和价格
   * * 订单验价
   * * 优惠券微服务-获取优惠券
   * * 验证价格
   * * 锁定优惠券
   * * 锁定商品库存
   * * 创建订单对象
   * * 创建子订单对象
   * * 发送延迟消息-用于自动关单
   * * 创建支付信息-对接三方支付
   *
   * @param orderRequest
   * @return
   */
  @Override
  public JsonData confirmOrder(ConfirmOrderRequest orderRequest) {
    LoginUser loginUser = LoginInterceptor.threadLocal.get();
    String token = orderRequest.getToken();
    if(StringUtils.isBlank(token)){
      throw new BizException(BizCodeEnum.ORDER_CONFIRM_TOKEN_NOT_EXIST);
    }
    String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
    Long executeResult = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Collections.singletonList(String.format(CacheKey.SUBMIT_ORDER_TOKEN_KEY, loginUser.getId())),token);
    if(0L == executeResult){
      throw new BizException(BizCodeEnum.ORDER_CONFIRM_TOKEN_EQUAL_FAIL);
    }
    String outTradeNo = CommonUtil.getStringNumRandom(32);
    ProductOrderAddressVo userAddress = getUserAddress(orderRequest.getAddressId());
    List<OrderItemVo> orderProduct = getOrderProduct(orderRequest.getProductIdList());
    this.checkPrice(orderProduct, orderRequest);
    this.lockCouponRecord(orderRequest, outTradeNo);
    this.lockProductStock(orderProduct, outTradeNo);
    ProductOrderDO productOrderDO = this.saveProductAndItem(outTradeNo, orderRequest, loginUser, userAddress, orderProduct);
    OrderMessage orderMessage = new OrderMessage();
    orderMessage.setOrderId(productOrderDO.getId());
    rabbitTemplate.convertAndSend(orderMqConfig.getEventExchange(), orderMqConfig.getOrderReleaseDelayRoutingKey(), orderMessage);
    //创建支付
    PayInfoVO payInfoVO = new PayInfoVO(productOrderDO.getOutTradeNo(), productOrderDO.getPayAmount(), productOrderDO.getPayType(),
        orderRequest.getClientType(), "全芯智宇科技公司", "商品下单支付", TimeOutConstant.ORDER_PAY_TIME_OUT_MILLS);
    String pay = payFactory.pay(payInfoVO);
    if (StringUtils.isNotEmpty(pay)) {
      return JsonData.buildSuccess(pay);
    }
    return JsonData.buildResult(BizCodeEnum.PAY_ORDER_FAIL);
  }

  private ProductOrderDO saveProductAndItem(String outTradeNo, ConfirmOrderRequest orderRequest, LoginUser loginUser,
                                            ProductOrderAddressVo userAddress, List<OrderItemVo> orderProduct) {
    ProductOrderDO productOrderDO = Builder.of(ProductOrderDO::new)
        .with(ProductOrderDO::setOutTradeNo, outTradeNo)
        .with(ProductOrderDO::setState, ProductOrderStateEnum.NEW.name())
        .with(ProductOrderDO::setCreateTime, new Date())
        .with(ProductOrderDO::setTotalAmount, orderRequest.getTotalAmount())
        .with(ProductOrderDO::setPayAmount, orderRequest.getRealPayAmount())
        .with(ProductOrderDO::setNickname, loginUser.getName()).with(ProductOrderDO::setHeadImg, loginUser.getHeadImg())
        .with(ProductOrderDO::setUserId, loginUser.getId()).with(ProductOrderDO::setDel, 0)
        .with(ProductOrderDO::setUpdateTime, new Date()).with(ProductOrderDO::setOrderType, ProductOrderTypeEnum.DAILY.name())
        .with(ProductOrderDO::setPayType, orderRequest.getPayType()).with(ProductOrderDO::setReceiverAddress, JSON.toJSONString(userAddress)).build();
    productOrderMapper.insert(productOrderDO);
    orderProduct.forEach(orderItemVo -> {
      ProductOrderItemDO productOrderItemDO = Builder.of(ProductOrderItemDO::new)
          .with(ProductOrderItemDO::setProductOrderId, productOrderDO.getId())
          .with(ProductOrderItemDO::setOutTradeNo, outTradeNo)
          .with(ProductOrderItemDO::setProductId, orderItemVo.getProductId())
          .with(ProductOrderItemDO::setProductImg, orderItemVo.getCoverImg())
          .with(ProductOrderItemDO::setProductName, orderItemVo.getTitle())
          .with(ProductOrderItemDO::setBuyNum, orderItemVo.getBuyNum())
          .with(ProductOrderItemDO::setCreateTime, productOrderDO.getCreateTime())
          .with(ProductOrderItemDO::setTotalAmount, orderItemVo.getTotalAmount())
          .with(ProductOrderItemDO::setAmount, orderItemVo.getAmount()).build();
      productOrderItemMapper.insert(productOrderItemDO);
    });
    return productOrderDO;
  }


  private void lockCouponRecord(ConfirmOrderRequest orderRequest, String outTradeNo) {
    Long couponRecordId = orderRequest.getCouponRecordId();
    if (couponRecordId != null && couponRecordId > 0L) {
      LockCouponRecordRequest lockCouponRecordRequest = new LockCouponRecordRequest();
      lockCouponRecordRequest.setLockCouponRecordIds(Lists.newArrayList(orderRequest.getCouponRecordId()));
      lockCouponRecordRequest.setOrderOutTradeNo(outTradeNo);
      JsonData jsonData = couponFeignClient.lockCouponRecord(lockCouponRecordRequest);
      if (jsonData.getCode() != 0) {
        throw new BizException(BizCodeEnum.COUPON_RECORD_LOCK_FAIL);
      }
    }
  }

  private void lockProductStock(List<OrderItemVo> orderProduct, String outTradeNo) {
    LockProductRequest lockProductRequest = new LockProductRequest();
    List<OrderItemRequest> orderItemRequests = orderProduct.stream().map(obj -> {
      OrderItemRequest orderItemRequest = new OrderItemRequest();
      orderItemRequest.setProductId(obj.getProductId());
      orderItemRequest.setBuyNum(obj.getBuyNum());
      return orderItemRequest;
    }).collect(Collectors.toList());
    lockProductRequest.setOrderItemRequests(orderItemRequests);
    lockProductRequest.setOrderOutTradeNo(outTradeNo);
    JsonData jsonData = productFeignClient.lockProductStock(lockProductRequest);
    if (jsonData.getCode() != 0) {
      throw new BizException(BizCodeEnum.ORDER_CONFIRM_ADD_STOCK_TASK_FAIL);
    }
  }


  public ProductOrderAddressVo getUserAddress(Long addressId) {
    JsonData jsonData = userFeignClient.addressDetail(addressId);
    if (jsonData.getCode() != 0) {
      throw new BizException(BizCodeEnum.ADDRESS_NOT_EXIT);
    }
    return jsonData.getData(new TypeReference<ProductOrderAddressVo>() {
    });
  }

  public List<OrderItemVo> getOrderProduct(List<Long> productIdList) {
    JsonData jsonData = productFeignClient.confirmOrderCartItem(productIdList);
    if (jsonData.getCode() != 0) {
      throw new BizException(BizCodeEnum.ORDER_CONFIRM_CART_ITEM_NOT_EXIST);
    }
    return jsonData.getData(new TypeReference<List<OrderItemVo>>() {
    });
  }

  public void checkPrice(List<OrderItemVo> orderProduct, ConfirmOrderRequest orderRequest) {
    BigDecimal allPrice = orderProduct.stream().map(OrderItemVo::getTotalAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
    //获取优惠券 判断优惠券是否可用
    Long couponRecordId = orderRequest.getCouponRecordId();
    BigDecimal price = BigDecimal.ZERO;
    if (null != couponRecordId && couponRecordId != 0L) {
      JsonData couponRecordData = couponFeignClient.findUserCouponRecordById(couponRecordId);
      if (couponRecordData.getCode() != 0) {
        throw new BizException(BizCodeEnum.COUPON_UNAVAILABLE);
      }
      OrderCouponRecordVo couponRecord = couponRecordData.getData(new TypeReference<OrderCouponRecordVo>() {
      });
      BigDecimal conditionPrice = couponRecord.getConditionPrice();
      long endTime = couponRecord.getEndTime().getTime();
      long startTime = couponRecord.getStartTime().getTime();
      long time = CommonUtil.getCurrentTimestamp();
      if (conditionPrice.compareTo(allPrice) > 0 || time > endTime || time < startTime) {
        throw new BizException(BizCodeEnum.ORDER_CONFIRM_COUPON_FAIL);
      }
      price = couponRecord.getPrice();
    }
    BigDecimal paidAmount = BigDecimal.ZERO;
    if (allPrice.compareTo(price) > 0) {
      paidAmount = allPrice.subtract(price);
    }
    if (orderRequest.getRealPayAmount().compareTo(paidAmount) != 0) {
      throw new BizException(BizCodeEnum.ORDER_CONFIRM_PRICE_FAIL);
    }
  }


  @Override
  public JsonData queryProductOrderState(String outTradeNo) {
    ProductOrderDO productOrderDO = productOrderMapper.selectOne(new QueryWrapper<ProductOrderDO>().eq("out_trade_no", outTradeNo));
    if (null == productOrderDO) {
      throw new BizException(BizCodeEnum.ORDER_CONFIRM_NOT_EXIST);
    }
    return JsonData.buildSuccess(productOrderDO.getState());
  }

  @Override
  public boolean releaseOrder(Long orderId) {
    ProductOrderDO productOrderDO = productOrderMapper.selectById(orderId);
    if (productOrderDO == null) {
      log.info("订单不存在 释放此消息{}", orderId);
      return true;
    }
    if (Objects.equals(productOrderDO.getState(), ProductOrderStateEnum.PAY.name())) {
      log.info("直接确认支付 释放此消息{}", orderId);
      return true;
    }
    PayInfoVO payInfoVO = new PayInfoVO();
    payInfoVO.setPayType(productOrderDO.getPayType());
    payInfoVO.setOutTradeNo(productOrderDO.getOutTradeNo());
    String payResult = payFactory.queryPaySuccess(payInfoVO);
    if (StringUtils.isEmpty(payResult)) {
      log.info("还是未支付状态 修改此订单状态为已超时{}", productOrderDO);
      productOrderMapper.updateOrderPayState(productOrderDO.getOutTradeNo(), ProductOrderStateEnum.CANCEL.name(), ProductOrderStateEnum.NEW.name());
      return true;
    } else {
      log.info("订单已支付{}", productOrderDO);
      productOrderMapper.updateOrderPayState(productOrderDO.getOutTradeNo(), ProductOrderStateEnum.PAY.name(), ProductOrderStateEnum.NEW.name());
      return true;
    }
  }

  @Override
  public JsonData handlerOrderCallBack(ProductOrderPayTypeEnum productOrderPayTypeEnum, Map<String, String> requestMap) {
    if (productOrderPayTypeEnum.name().equals(ProductOrderPayTypeEnum.ALIPAY.name())) {
      String outTradeNo = requestMap.get("out_trade_no"); //商户订单号
      String tradeStatus = requestMap.get("trade_status"); //支付宝支付状态
      if ("TRADE_SUCCESS".equalsIgnoreCase(tradeStatus)) {
        //支付成功
        int rows = productOrderMapper.updateOrderPayState(outTradeNo, ProductOrderStateEnum.PAY.name(), ProductOrderStateEnum.NEW.name());
        if (rows == 1) {
          return JsonData.buildSuccess();
        }
      }
    } else if (productOrderPayTypeEnum.name().equals(ProductOrderPayTypeEnum.WECHAT.name())) {
    }
    return JsonData.buildResult(BizCodeEnum.PAY_ORDER_CALLBACK_NOT_SUCCESS);
  }

  @Override
  public Map<String, Object> pageOrder(Integer page, Integer size, String state) {
    Page<ProductOrderDO> pageInfo = new Page<>(page, size);
    LoginUser loginUser = LoginInterceptor.threadLocal.get();
    Map<String, Object> pageMap = Maps.newHashMap();
    Page<ProductOrderDO> productOrderDOPage = null;
    if (StringUtils.isBlank(state)) {
      productOrderDOPage = productOrderMapper.selectPage(pageInfo, new QueryWrapper<ProductOrderDO>().eq("user_id", loginUser.getId()).eq("del", 0));
    } else {
      productOrderDOPage = productOrderMapper.selectPage(pageInfo, new QueryWrapper<ProductOrderDO>().eq("user_id", loginUser.getId()).eq("del", 0).eq("state", state));
    }
    pageMap.put("total_record", productOrderDOPage.getTotal());
    pageMap.put("total_page", productOrderDOPage.getPages());
    pageMap.put("current_data", productOrderDOPage.getRecords().stream().map(this::beanProcess).collect(Collectors.toList()));
    return pageMap;
  }

  private ProductOrderVo beanProcess(ProductOrderDO orderDO) {
    ProductOrderVo productOrderVo = new ProductOrderVo();
    BeanUtils.copyProperties(orderDO, productOrderVo);
    List<ProductOrderItemDO> productOrderItemList = productOrderItemMapper.selectList(new QueryWrapper<ProductOrderItemDO>().eq("product_order_id", orderDO.getId()).eq("out_trade_no", orderDO.getOutTradeNo()));
    productOrderVo.setProductOrderItemVos(productOrderItemList.stream().map(productOrderItemDO -> {
      ProductOrderItemVo productOrderItemVo = new ProductOrderItemVo();
      BeanUtils.copyProperties(productOrderItemDO, productOrderItemVo);
      return productOrderItemVo;
    }).collect(Collectors.toList()));
    return productOrderVo;
  }

  @Transactional(rollbackFor = Exception.class)
  @Override
  public JsonData repayOrder(RepayOrderRequest repayOrderRequest) {
    LoginUser loginUser = LoginInterceptor.threadLocal.get();
    ProductOrderDO productOrderDO = Optional.ofNullable(productOrderMapper.selectOne(new QueryWrapper<ProductOrderDO>()
        .eq("out_trade_no", repayOrderRequest.getOutTradeNo()).eq("del", 0)
        .eq("state", ProductOrderStateEnum.NEW).eq("user_id", loginUser.getId()))).orElseThrow(() -> new BizException(BizCodeEnum.ORDER_ORDER_RENEWED_PAY));
    //判断时间是否允许重新支付
    //订单存活时间 需要加一分钟加几秒
    long orderLiveTime = CommonUtil.getCurrentTimestamp() - productOrderDO.getCreateTime().getTime() + 70 * 1000;
    if (orderLiveTime > TimeOutConstant.ORDER_PAY_TIME_OUT_MILLS) {
      throw new BizException(BizCodeEnum.PAY_ORDER_PAY_TIMEOUT);
    }
    long surplusOrderLiveTime = TimeOutConstant.ORDER_PAY_TIME_OUT_MILLS - orderLiveTime;
    PayInfoVO payInfoVO = new PayInfoVO(productOrderDO.getOutTradeNo(), productOrderDO.getPayAmount(), productOrderDO.getPayType(),
        repayOrderRequest.getClientType(), "全芯智宇科技公司", "商品下单支付", surplusOrderLiveTime);
    String pay = payFactory.pay(payInfoVO);
    if (StringUtils.isEmpty(pay)) {
      throw new BizException(BizCodeEnum.PAY_ORDER_FAIL);
    }
    //修改订单信息
    productOrderMapper.updatePayTypeById(repayOrderRequest.getPayType(), productOrderDO.getId());
    return JsonData.buildSuccess(pay);
  }
}
